﻿using FluentValidation.AspNetCore;
using FluentValidation;
//using MediatR;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Serilog;
using StackExchange.Redis;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.Data.SqlClient;
using Apex.ASPNETCore;
using Apex.Commons;
using Apex.IdentityAuth.Swagger;
//using Apex.Commons.JsonConverters;
using Apex.EventBus;
using Apex.Commons.ConfigurationCenter;
using Apex.EventBus.Options;
using Apex.EventBus.Extensions;
using Apex.Commons.ServiceRegistrationCenter;
using Apex.ASPNETCore.GlobalException;
using Apex.ASPNETCore.UnitOfWork;

using MediatR;
using System.Text.Json.Serialization;
using Apex.IdentityAuth.JWT;
using System.Net;
using Microsoft.Extensions.Options;
//using Apex.JWT;

namespace CommonInitializer;
/// <summary>
/// 扩展方法类，用于配置ASP.NET Core Web应用程序中的额外服务
/// </summary>
public static class WebApplicationBuilderExtensions
{
    /// <summary>
    /// 链接config表，从表中获取各个中间件连接信息
    /// </summary>
    /// <param name="builder"></param>
    public static void ConfigureDbConfiguration(this WebApplicationBuilder builder)
    {
        builder.Host.ConfigureAppConfiguration((hostCtx, configBuilder) =>
        {
            // 不能使用ConfigureAppConfiguration中的configBuilder去读取配置，否则就循环调用了，因此这里直接自己去读取配置文件
            // var configRoot = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
            // string connStr = configRoot.GetValue<string>("DefaultDB:ConnStr");

            // 配置表T_Config 的数据库连接信息，从系统变量环境中获取
            string connStr = builder.Configuration.GetValue<string>("DefaultDB:ConnStr");
            // 将数据库连接信息添加到应用程序配置中，以便后续可以从数据库中读取各个中间件的配置信息
            configBuilder.AddDbConfiguration(() => new SqlConnection(connStr), reloadOnChange: true, reloadInterval: TimeSpan.FromSeconds(5));
        });
    }

    /// <summary>
    /// 配置各类服务
    /// </summary>
    /// <param name="builder">Web应用程序构建器</param>
    /// <param name="initOptions">初始化选项</param>
    public static void ConfigureExtraServices(this WebApplicationBuilder builder, InitializerOptions initOptions)
    {
        // 获取服务集合和配置信息
        IServiceCollection services = builder.Services;
        IConfiguration configuration = builder.Configuration;
        // 获取所有引用的程序集
        var assemblies = ReflectionHelper.GetAllReferencedAssemblies();

        #region 批量注册服务（更优雅）
        services.RunModuleInitializers(assemblies);
        #endregion
        #region 自动为所有的DbContext注册连接配置
        services.AddAllDbContexts(
            // 如果不配置，使用DBContext内的OnConfiguring的配置
            // 如果配置了，覆盖DBContext内的OnConfiguring的配置
            ctx =>
            {
                //连接字符串如果放到appsettings.json中，会有泄密的风险
                //如果放到UserSecrets中，每个项目都要配置，很麻烦
                //因此这里推荐放到环境变量中。
                string connStr = builder.Configuration.GetConnectionString("QuartzConnStr");
                ctx.UseSqlServer(connStr);
            },
            assemblies);
        #endregion

        #region 鉴权校验
        /*
         * 只要需要校验Authentication报文头的地方（非IdentityService.WebAPI项目）也需要启用这些
         * IdentityService项目还需要启用AddIdentityCore
         */

        // 用于配置授权服务：定义应用程序中的授权策略的，它允许你在应用程序中定义谁能访问哪些资源的规则
        // 通过这个方法，你可以配置应用程序的授权策略，例如基于角色、基于声明等。
        // 在使用授权服务之前，通常需要先添加身份验证服务。
        //builder.Services.AddAuthorization();
        //// 添加身份验证服务：身份验证服务是用来验证请求中的用户身份的
        //// 它允许你配置不同的身份验证方案，例如 JWT、Cookie 等
        //// 通过这个方法，你可以配置应用程序的身份验证方式，并指定如何验证用户的身份
        //// 添加身份验证服务后，可以使用 UseAuthentication() 方法将身份验证中间件添加到请求处理管道中，以便在请求到达控制器之前对用户进行身份验证
        //builder.Services.AddAuthentication();
        //// 从T_Configs表中获取配置，并用JWTOptions接收
        //JWTOptions jwtOpt = configuration.GetSection("JWT").Get<JWTOptions>();
        //// 添加jwt的身份校验
        //builder.Services.AddJWTAuthentication(jwtOpt);
        ////启用Swagger中的【Authorize】按钮。这样就不用每个项目的AddSwaggerGen中单独配置了
        //builder.Services.Configure<SwaggerGenOptions>(c =>
        //{
        //    c.AddAuthenticationHeader();
        //});
        #endregion

        #region AddMediatR 注册
        services.AddMediatR(assemblies);
        #endregion

        #region MVC配置
        //现在不用手动AddMVC了，因此把文档中的services.AddMvc(options =>{})改写成Configure<MvcOptions>(options=> {})这个问题很多都类似
        services.Configure<MvcOptions>(options =>
        {
            options.Filters.Add<GlobalExceptionFilter>(); // 自定义全局异常处理
            options.Filters.Add<UnitOfWorkFilter>();
        });
        #endregion

        #region Json配置
        // 笔记：https://www.yuque.com/u41103341/nng8k7/ie1w3nk7obf3nsfg
        services.Configure<JsonOptions>(options =>
        {
            //设置时间格式。而非“2008-08-08T08:08:08”这样的格式
            options.JsonSerializerOptions.Converters.Add(new DateTimeJsonConverter("yyyy-MM-dd HH:mm:ss"));

            // 设置属性名称为驼峰式
            options.JsonSerializerOptions.PropertyNamingPolicy = null;

            // 忽略空值：空字段不响应Response
            //options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;

            // 允许最后一个元素后面可以写逗号不报错
            options.JsonSerializerOptions.AllowTrailingCommas = true;

            // 忽略循环引用
            options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
        });
        #endregion

        #region 跨域配置
        //services.AddCors(options =>
        //    {
        //        //更好的在Program.cs中用绑定方式读取配置的方法：https://github.com/dotnet/aspnetcore/issues/21491
        //        //不过比较麻烦。
        //        var corsOpt = configuration.GetSection("Cors").Get<CorsSettings>();
        //        string[] urls = corsOpt.Origins;
        //        options.AddDefaultPolicy(builder => builder.WithOrigins(urls)
        //                .AllowAnyMethod().AllowAnyHeader().AllowCredentials());
        //    }
        //);
        #endregion

        #region 日志配置
        // 添加日志记录服务
        services.AddLogging(builder =>
        {
            // 配置 Serilog 日志记录器
            Log.Logger = new LoggerConfiguration()
                // 设置日志记录的最低级别
                // .MinimumLevel.Information().Enrich.FromLogContext()
                // 将日志写入控制台
                .WriteTo.Console()
                // 将日志写入文件，文件路径从初始化选项中获取
                .WriteTo.File(initOptions.LogFilePath, rollingInterval: RollingInterval.Day, retainedFileCountLimit: 3)
                .CreateLogger();
            // 将 Serilog 集成到日志记录器中
            builder.AddSerilog();
        });
        #endregion

        #region FluentValidation 校验配置
        //旧的配置方式，已经被弃用
        //services.AddFluentValidation(opt =>
        //{
        //    opt.RegisterValidatorsFromAssemblies(assemblies);
        //});
        //新的配置方式
        services.AddFluentValidationAutoValidation(); // 启用自动验证功能。FluentValidation自动检测控制器的参数和ViewModel，根据定义的验证规则进行验证。简化代码，不再需要在每个控制器的动作方法中手动调用验证逻辑。
        services.AddValidatorsFromAssemblies(assemblies); // 将当前应用程序域中的程序集中定义的验证器注册到DI容器中
        #endregion

        #region JWT配置信息绑定
        services.Configure<JWTOptions>(configuration.GetSection("JWT"));
        #endregion

        #region RabbitMQ配置
        // 注册 RabbitMQ 相关的配置信息
        // services.Configure<IntegrationEventRabbitMQOptions>(configuration.GetSection("RabbitMQ")); 从T_Configs表中获取
        services.Configure<IntegrationEventRabbitMQOptions>(e => // 手动指定
        {
            // 设置 RabbitMQ 连接信息
            e.HostName = "10.100.80.92"; // localhost 10.100.80.92
            e.UserName = "raoly";
            e.Password = "123";
            e.ExchangeName = "MyTest";
        });

        // 添加事件总线相关的服务
        services.AddEventBus(initOptions.EventBusQueueName, assemblies);
        #endregion

        #region Redis配置
        // 从T_Configs表中获取redis连接信息
        string redisConnStr = configuration.GetValue<string>("Redis:ConnStr");
        // 连接方式一：使用Redis分布式缓存（杨中科老师在缓存教程中给的配置方式）
        services.AddStackExchangeRedisCache(options =>
        {
            options.Configuration = redisConnStr; //连接哪个redis服务器
            //options.InstanceName = "rly_"; //避免分布式缓存的key与其他项目的key混乱，建议大家给key添加一个前缀
        });
        // 连接方式二：使用Redis分布式缓存（杨中科老师在大项目中教程中给的配置方式，但是好像将缓存写入到redis中并未生效）
        //IConnectionMultiplexer redisConnMultiplexer = ConnectionMultiplexer.Connect(redisConnStr);// 创建一个连接到Redis服务器的连接器
        //services.AddSingleton(typeof(IConnectionMultiplexer), redisConnMultiplexer); // Redis服务器的连接器，注册单例服务
        #endregion

        #region 转发头选项
        // 在使用反向代理服务器（如Nginx、Apache等）时
        // 可能会使用转发头来传递客户端的原始信息。这里的配置告诉应用程序要处理所有传入的转发头
        /* 功能如下：
         * 客户端识别：应用程序可以从转发头中提取客户端的原始IP地址、端口号、协议等信息，以识别请求的真实来源。这对于记录日志、实施访问控制、进行安全性检查等操作非常重要
         * 安全性检查：通过识别转发头信息，应用程序可以检查请求是否来自可信任的代理服务器，从而增强应用程序的安全性。例如，应用程序可以验证请求的来源是否是预期的代理服务器，并拒绝来自未知代理的请求
         * 日志记录：应用程序可能会将转发头中的信息包括在内，以记录请求的完整路径。这有助于跟踪请求的传播路径，分析应用程序的访问模式以及排查潜在的问题
         * HTTPS 重定向：如果反向代理在处理SSL（HTTPS）流量时会将请求转发为HTTP形式，应用程序可以从转发头中检测到原始请求是通过HTTPS发起的，并将请求重定向至HTTPS链接，以确保安全性和数据保护
         * 请求处理：某些请求处理逻辑可能需要根据来自代理的转发头信息做出不同的响应。例如，应用程序可能会基于不同的代理标识符或特定的请求头信息执行不同的业务逻辑
         */
        services.Configure<ForwardedHeadersOptions>(options =>
        {
            options.ForwardedHeaders = ForwardedHeaders.All;
        });
        #endregion

        #region 控制器服务
        // 添加控制器服务，用于处理 HTTP 请求
        services.AddControllers()
        // 添加自定义验证逻辑
        .AddCustomValidation()
        // 添加 XML 序列化器
        .AddXmlSerializerFormatters();
        #endregion
    }
}

